﻿using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interactivity;
using System.Windows.Media;
using System.Windows.Shapes;

// ResizeDragBehavior Created by Andries van der Meulen. http://resizedragbehavior.codeplex.com/
namespace ResizeDragBehaviorWPF
{
    /// <summary>
    /// Class for resizing VisualElements.
    /// </summary>
    public class ResizeDragBehavior : Behavior<FrameworkElement>
    {
        /// <summary>
        /// The available directions where the element can resize to.
        /// </summary>
        public enum ResizeDirections
        {
            None,
            Right,
            Left,
            Up,
            Down
        }

        /// <summary>
        /// Flag to see if resizing is in progress.
        /// </summary>
        private bool _IsResizing = false;

        /// <summary>
        /// Point to remember the mouse movement change.
        /// </summary>
        private Point _InitialResizePoint;

        /// <summary>
        /// Point for storing the size of the resizeable element.
        /// </summary>
        private Size _ResizeElementWindowSize = new Size();

        /// <summary>
        /// When not using ShowsPreview, the resize delta contains the eventually size change.
        /// </summary>
        private double _resizeDeltaX, _resizeDeltaY = 0;

        /// <summary>
        /// The AssociatedObject wich will be dragged for the resizing.
        /// Reference wil be kept in this property so events can be descriped on the detach.
        /// </summary>
        private FrameworkElement DragElement;

        /// <summary>
        /// If not showing preview, then this element will be used to show the resize action.
        /// </summary>
        private Rectangle PreviewDragElement;

        /// <summary>
        /// The TranslateTransform is used to reposition the PreviewDragElement when dragging.
        /// </summary>
        private TranslateTransform PreviewDragElementTranslateTransform;

        /// <summary>
        /// The overlay holding the PreviewDragElement;
        /// </summary>
        private IDisposable DragElementOverlay;

        /// <summary>
        /// The elementname which has to resize.
        /// </summary>
        [Category("Common Properties"), Description("The targeted element that must be resized."), CustomPropertyValueEditorAttribute(CustomPropertyValueEditor.Element)]
        public string ResizeElementName
        {
            get { return (string)GetValue(ResizeElementNameProperty); }
            set { SetValue(ResizeElementNameProperty, value); }
        }
        public static readonly DependencyProperty ResizeElementNameProperty = DependencyProperty.Register("ResizeElementName", typeof(string), typeof(ResizeDragBehavior), new PropertyMetadata(string.Empty, ResizeElementNameChanged));

        /// <summary>
        /// The elementname which has to resize.
        /// </summary>
        [Category("Common Properties"), Description("Wheter the resizing must be done in realtime."), EditorBrowsable(EditorBrowsableState.Advanced), DefaultValue(true)]
        public bool ShowsPreview
        {
            get { return _ShowsPreview; }
            set { _ShowsPreview = value; }
        }
        private bool _ShowsPreview = true;

        /// <summary>
        /// Gets or Sets the value whether this behavior is active.
        /// </summary>
        [Category("Common Properties"), Description("The targeted element that must be resized."), EditorBrowsable(EditorBrowsableState.Advanced), DefaultValue(true)]
        public bool IsEnabled
        {
            get { return _IsEnabled; }
            set { _IsEnabled = value; }
        }
        private bool _IsEnabled = true;

        /// <summary>
        /// The element which has to resize.
        /// </summary>
        private FrameworkElement ResizeElement { get; set; }

        /// <summary>
        /// The direction the resizing must take place.
        /// </summary>
        [Category("Common Properties"), Description("The direction the resizing must take place.")]
        public ResizeDirections ResizeDirection
        {
            get { return (ResizeDirections)GetValue(ResizeDirectionProperty); }
            set { SetValue(ResizeDirectionProperty, value); }
        }
        public static readonly DependencyProperty ResizeDirectionProperty = DependencyProperty.Register("ResizeDirection", typeof(ResizeDirections), typeof(ResizeDragBehavior), new PropertyMetadata(ResizeDirections.None, ResizeDirectionChanged));

        /// <summary>
        /// Wheter the max height or width of the resize element must be removed when starting to resize.
        /// </summary>
        [Category("Common Properties"), Description("Wheter the max height or width of the resize element must be removed when starting to resize."), DefaultValue(false)]
        public bool RemoveMax
        {
            get { return (bool)GetValue(RemoveMaxProperty); }
            set { SetValue(RemoveMaxProperty, value); }
        }
        public static readonly DependencyProperty RemoveMaxProperty = DependencyProperty.Register("RemoveMax", typeof(bool), typeof(ResizeDragBehavior), new PropertyMetadata(false));

        /// <summary>
        /// Sets the mouse cursor.
        /// </summary>
        private void SetResizeDirections()
        {
            if (DragElement == null) return;

            switch (ResizeDirection)
            {
                case ResizeDirections.Left:
                case ResizeDirections.Right:
                    DragElement.Cursor = Cursors.SizeWE;
                    break;
                case ResizeDirections.Up:
                case ResizeDirections.Down:
                    DragElement.Cursor = Cursors.SizeNS;
                    break;
                default:
                    DragElement.Cursor = Cursors.Arrow;
                    break;
            }
        }

        /// <summary>
        /// Event when the behavior is attached to a element.
        /// </summary>
        protected override void OnAttached()
        {
            base.OnAttached();

            if (!IsEnabled) return;

            DragElement = AssociatedObject;

            DragElement.MouseLeftButtonDown += DragElementMouseLeftButtonDown;
            DragElement.MouseLeftButtonUp += DragElementMouseLeftButtonUp;
            DragElement.MouseMove += DragElementMouseMove;

            SetResizeDirections();
        }

        /// <summary>
        /// Event when the behavior is detached from the element.
        /// </summary>
        protected override void OnDetaching()
        {
            base.OnDetaching();

            DragElement.MouseLeftButtonDown -= DragElementMouseLeftButtonDown;
            DragElement.MouseLeftButtonUp -= DragElementMouseLeftButtonUp;
            DragElement.MouseMove -= DragElementMouseMove;
        }

        /// <summary>
        /// Event when the selected resize direction changed.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private static void ResizeDirectionChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            ((ResizeDragBehavior)sender).SetResizeDirections();
        }

        /// <summary>
        /// Event when the selected resize elementname changed.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private static void ResizeElementNameChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            ResizeDragBehavior behavior = sender as ResizeDragBehavior;
            if (behavior.DragElement != null)
                behavior.ResizeElement = behavior.DragElement.FindName(behavior.ResizeElementName) as FrameworkElement;
        }

        /// <summary>
        /// Event when the left mouse button is down on the dragging element.
        /// Starts resizing.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void DragElementMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (ResizeElement == null)
                ResizeElement = DragElement.FindName(ResizeElementName) as FrameworkElement;

            if (CanResize)
            {
                // Capture the mouse
                ((FrameworkElement)sender).CaptureMouse();

                // Store the start position
                _InitialResizePoint = e.GetPosition(ResizeElement.Parent as UIElement);
                _ResizeElementWindowSize.Width = (!double.IsNaN(ResizeElement.Width) ? ResizeElement.Width : ResizeElement.ActualWidth);
                _ResizeElementWindowSize.Height = (!double.IsNaN(ResizeElement.Height) ? ResizeElement.Height : ResizeElement.ActualHeight);

                // Set resizing to true
                _IsResizing = true;

                //If size change is not set immediate, use the PreviewDragElement.
                if (!ShowsPreview)
                {
                    if (PreviewDragElementTranslateTransform == null)
                        PreviewDragElementTranslateTransform = new TranslateTransform();

                    PreviewDragElementTranslateTransform.X = 0;
                    PreviewDragElementTranslateTransform.Y = 0;

                    if (PreviewDragElement == null)
                        PreviewDragElement = new Rectangle()
                        {
                            Fill = new SolidColorBrush(Color.FromArgb(130, 0, 0, 0)),
                            RenderTransform = PreviewDragElementTranslateTransform
                        };

                    PreviewDragElement.Width = DragElement.ActualWidth;
                    PreviewDragElement.Height = DragElement.ActualHeight;

                    if (DragElementOverlay == null)
                        DragElementOverlay = OverlayAdorner<Rectangle>.Overlay(DragElement, PreviewDragElement);
                }
            }
        }

        /// <summary>
        /// Event when the left mouse button is up on the dragging element.
        /// Stops resizing.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void DragElementMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            if (_IsResizing)
            {
                // Release the mouse
                ((FrameworkElement)sender).ReleaseMouseCapture();

                // Set resizing to false
                _IsResizing = false;

                //If size change is not set immediate, change it now.
                if (!ShowsPreview)
                {
                    SetSize(_resizeDeltaX, _resizeDeltaY);
                    _resizeDeltaX = 0;
                    _resizeDeltaY = 0;

                    if (DragElementOverlay != null)
                        DragElementOverlay.Dispose();

                    DragElementOverlay = null;
                }
            }
        }

        /// <summary>
        /// Event when the mouse moves on the dragging element.
        /// Calculates the resizing when in dragging mode.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void DragElementMouseMove(object sender, MouseEventArgs e)
        {
            if (!CanResize || !_IsResizing) return;

            Resize(e.GetPosition(ResizeElement.Parent as UIElement));

            _InitialResizePoint = e.GetPosition(ResizeElement.Parent as UIElement);
        }

        /// <summary>
        /// Read-Only. Checks if all required properties are available for the resize ability.
        /// </summary>
        private bool CanResize
        {
            get
            {
                if (ResizeDirection == ResizeDirections.None)
                    return false;
                if (DragElement == null)
                    return false;
                if (ResizeElementName == null)
                    return false;

                return true;
            }
        }

        /// <summary>
        /// Calculates the distance between the two points and resizes the ResizeElement.
        /// </summary>
        /// <param name="mousePosition">Point referenced to element.</param>
        private void Resize(Point mousePosition)
        {
            if (_InitialResizePoint == null)
                return;

            double deltaX = mousePosition.X - _InitialResizePoint.X;
            double deltaY = mousePosition.Y - _InitialResizePoint.Y;

            if (!ShowsPreview)
            {
                _resizeDeltaX += deltaX;
                _resizeDeltaY += deltaY;

                switch (ResizeDirection)
                {
                    case ResizeDirections.Right:
                    case ResizeDirections.Left:
                        PreviewDragElementTranslateTransform.X += deltaX;
                        break;

                    case ResizeDirections.Up:
                    case ResizeDirections.Down:
                        PreviewDragElementTranslateTransform.Y += deltaY;
                        break;
                }
            }
            else
            {
                SetSize(deltaX, deltaY);
            }
        }

        /// <summary>
        /// Resizes the ResizeElement with the provided delta in combination with the set ResizeDirection.
        /// </summary>
        /// <param name="deltaX">The change in width.<param>
        /// <param name="deltaY">The change in height.</param>
        private void SetSize(double deltaX, double deltaY)
        {
            if (ResizeElement == null)
                return;

            switch (ResizeDirection)
            {
                case ResizeDirections.Right:
                    if (double.IsNaN(ResizeElement.Width))
                        ResizeElement.Width = ResizeElement.ActualWidth;

                    if (RemoveMax && !double.IsInfinity(ResizeElement.MaxWidth))
                        ResizeElement.MaxWidth = double.PositiveInfinity;

                    if (ResizeElement.Width - deltaX > 0)
                        ResizeElement.Width -= deltaX;
                    break;

                case ResizeDirections.Left:
                    if (double.IsNaN(ResizeElement.Width))
                        ResizeElement.Width = ResizeElement.ActualWidth;

                    if (RemoveMax && !double.IsInfinity(ResizeElement.MaxWidth))
                        ResizeElement.MaxWidth = double.PositiveInfinity;

                    if (ResizeElement.Width + deltaX > 0)
                        ResizeElement.Width += deltaX;
                    break;

                case ResizeDirections.Up:
                    if (double.IsNaN(ResizeElement.Height))
                        ResizeElement.Height = ResizeElement.ActualHeight;

                    if (RemoveMax && !double.IsInfinity(ResizeElement.MaxHeight))
                        ResizeElement.MaxHeight = double.PositiveInfinity;

                    if (ResizeElement.Height + deltaY > 0)
                        ResizeElement.Height += deltaY;
                    break;

                case ResizeDirections.Down:
                    if (double.IsNaN(ResizeElement.Height))
                        ResizeElement.Height = ResizeElement.ActualHeight;

                    if (RemoveMax && !double.IsInfinity(ResizeElement.MaxHeight))
                        ResizeElement.MaxHeight = double.PositiveInfinity;

                    if (ResizeElement.Height - deltaY > 0)
                        ResizeElement.Height -= deltaY;
                    break;
            }
        }

    }
}